NGINX Ingress Controller を利用して EKS 上のアプリケーションを HTTPS で公開してみた (ACM を利用して NLB で SSL 終端するパターン )
EKS 上のアプリケーションを外部公開する際に使う Ingress コントローラとして ALB Load Balancer Controller や NGINX Ingress Controller が挙げられます。
主な違いはリバースプロキシとしてカスタマイズ性の高い nginx を利用するか AWS マネージドのサービスである Elastic Load Balancing を利用するかになります。
今回はこれらのコントローラの違いについて詳しく触れませんが、詳細は下記 Amazon Web Services ブログが参考になります。
- Kubernetes アプリケーションの公開 Part 2: AWS Load Balancer Controller
- Kubernetes アプリケーションの公開 Part 3: NGINX Ingress Controller
NGINX Ingress Controller を採用するとなった場合、 NLB で SSL 終端して ACM を利用するパターンと AWS 外で取得した証明書を Secret として登録して利用するパターンがあります。
ACM を利用することで、nginx を利用しつつも証明書の管理や更新を AWS に任せることができます。
今回は NGINX Ingress Controller を利用しつつ、NLB で SSL 終端するパターンを試してみました。
EKS クラスター作成
eksctl でクラスターを作成します。
eksctl create cluster --name test-cluster --region ap-northeast-1 --version 1.24 --vpc-cidr 10.0.0.0/16 --without-nodegroup --with-oidc --zones ap-northeast-1a,ap-northeast-1c
続いてノードグループも作成します。
eksctl create nodegroup --cluster test-cluster --region ap-northeast-1 --name test-cluster-ng --node-ami-family AmazonLinux2 --node-type t3.medium --nodes 2 --nodes-min 1 --nodes-max 2 --node-private-networking
証明書の作成
次に利用する証明書を ACM で発行します。
今回はホストベースルーティングを利用したかったためワイルドカード証明書を作成しました。
NGINX Ingress Controller のインストール
Installation Guide に沿って、 NGINX Ingress Controller をインストールします。
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/aws/deploy.yaml
ダウンロードしたファイルの一部を書き換えます。
ConfigMap(ingress-nginx-controller) の proxy-real-ip-cidr
を EKS クラスターがデプロイされている VPC の CIDR に変更します。
Service(ingress-nginx-controller) の service.beta.kubernetes.io/aws-load-balancer-ssl-cert
を ACM で発行した証明書の ARN に変更します。
※ ここで Service(ingress-nginx-controller) の externalTrafficPolicy
を Cluster
に設定しておかないと、 Ingress Controller が存在する Node にある Pod にしかトラフィックを流さなくなります。必ずしも変更する必要はありませんが、気になった場合は変更して下さい。
- Why is my worker node status "Unhealthy" when I use the NGINX Ingress Controller with Amazon EKS?
- Health check endpoints /healthz are constantly Unhealthy in AWS NLB target groups - 503 HTTP error
修正が終わったら、 NGINX Ingress Controller をデプロイします。
$ kubectl apply -f deploy.yaml namespace/ingress-nginx created serviceaccount/ingress-nginx created serviceaccount/ingress-nginx-admission created role.rbac.authorization.k8s.io/ingress-nginx created role.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrole.rbac.authorization.k8s.io/ingress-nginx created clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created rolebinding.rbac.authorization.k8s.io/ingress-nginx created rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created configmap/ingress-nginx-controller created service/ingress-nginx-controller created service/ingress-nginx-controller-admission created deployment.apps/ingress-nginx-controller created job.batch/ingress-nginx-admission-create created job.batch/ingress-nginx-admission-patch created ingressclass.networking.k8s.io/nginx created validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
アプリケーションのデプロイ
まず、アプリケーション用の名前空間を作成します。
$ kubectl create namespace apps namespace/apps created
次に Service リソースを作成します。
今回は Google が公開している hello と表示するだけのコンテナイメージを利用します。
apiVersion: apps/v1 kind: Deployment metadata: name: first namespace: apps labels: app.kubernetes.io/name: first spec: selector: matchLabels: app.kubernetes.io/name: first replicas: 2 template: metadata: labels: app.kubernetes.io/name: first spec: terminationGracePeriodSeconds: 0 containers: - name: first image: gcr.io/google-samples/hello-app:1.0 ports: - name: app-port containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: first namespace: apps labels: app.kubernetes.io/name: first spec: type: ClusterIP selector: app.kubernetes.io/name: first ports: - name: svc-port port: 3000 targetPort: app-port protocol: TCP --- apiVersion: apps/v1 kind: Deployment metadata: name: second namespace: apps labels: app.kubernetes.io/name: second spec: selector: matchLabels: app.kubernetes.io/name: second replicas: 2 template: metadata: labels: app.kubernetes.io/name: second spec: terminationGracePeriodSeconds: 0 containers: - name: second image: gcr.io/google-samples/hello-app:1.0 ports: - name: app-port containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: second namespace: apps labels: app.kubernetes.io/name: second spec: type: ClusterIP selector: app.kubernetes.io/name: second ports: - name: svc-port port: 3001 targetPort: app-port protocol: TCP
$ kubectl apply -f service.yaml deployment.apps/first created service/first created deployment.apps/second created service/second created
Ingress リソースをデプロイします。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: apps-ingress namespace: apps annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/use-regex: "true" nginx.ingress.kubernetes.io/proxy-body-size: 100m spec: rules: - host: first.masutaro99.com http: paths: - path: / pathType: Prefix backend: service: name: first port: number: 3000 - host: second.masutaro99.com http: paths: - path: / pathType: Prefix backend: service: name: second port: number: 3001
$ kubectl apply -f ingress.yaml ingress.networking.k8s.io/apps-ingress created
レコード作成
Route53 で設定したホスト名 (first.masutaro99.com, second.masutaro99.com) からNLB へのエイリアスレコードを作成します。
動作確認
それぞれのホストに対してリクエストを送り、それぞれ HTTPS で接続できていることを確認できました。
$ curl -v https://first.masutaro99.com * Trying 57.180.91.200:443... * Connected to first.masutaro99.com (57.180.91.200) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem * CApath: none * (304) (OUT), TLS handshake, Client hello (1): * (304) (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server did not agree to a protocol * Server certificate: * subject: CN=*.masutaro99.com * start date: Oct 22 00:00:00 2023 GMT * expire date: Nov 19 23:59:59 2024 GMT * subjectAltName: host "first.masutaro99.com" matched cert's "*.masutaro99.com" * issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02 * SSL certificate verify ok. > GET / HTTP/1.1 > Host: first.masutaro99.com > User-Agent: curl/7.79.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Sun, 22 Oct 2023 12:38:35 GMT < Content-Type: text/plain; charset=utf-8 < Content-Length: 62 < Connection: keep-alive < Hello, world! Version: 1.0.0 Hostname: first-7c5db465f5-jsmdb * Connection #0 to host first.masutaro99.com left intact
$ curl -v https://second.masutaro99.com * Trying 57.180.91.200:443... * Connected to second.masutaro99.com (57.180.91.200) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem * CApath: none * (304) (OUT), TLS handshake, Client hello (1): * (304) (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server did not agree to a protocol * Server certificate: * subject: CN=*.masutaro99.com * start date: Oct 22 00:00:00 2023 GMT * expire date: Nov 19 23:59:59 2024 GMT * subjectAltName: host "second.masutaro99.com" matched cert's "*.masutaro99.com" * issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02 * SSL certificate verify ok. > GET / HTTP/1.1 > Host: second.masutaro99.com > User-Agent: curl/7.79.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Sun, 22 Oct 2023 12:39:08 GMT < Content-Type: text/plain; charset=utf-8 < Content-Length: 63 < Connection: keep-alive < Hello, world! Version: 1.0.0 Hostname: second-64966675f9-dtr4g * Connection #0 to host second.masutaro99.com left intact